#region Using directives

/*
 * Copyright (c) 2007 Alan Dean.
 * All rights released.
 */

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Security.Permissions;

using Edenbrook;
using Edenbrook.Globalization;

#endregion

namespace Edenbrook.Telephony
{
    #region Overview
    /// <summary>
    /// Provides base class functionality for keypads.
    /// <para>This class cannot be instantiated directly.</para>
    /// </summary>
    /// 
    /// <remarks>None</remarks>
    /// 
    /// <permission cref="T:System.Security.Permissions.StrongNameIdentityPermission">
    /// Callers must be signed with the Edenbrook strong name key.
    /// </permission>
    #endregion
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]
    [StrongNameIdentityPermission(SecurityAction.Demand, PublicKey = PublicKey.Value)]
    public abstract class KeypadBase : Dictionary<Char, CharacterLocation>, IKeypad
    {
        // protected constructors

        #region protected KeypadBase(int, int)
        /// <overloads>
        /// Initializes a new instance of the <see cref="T:Edenbrook.Telephony.KeypadBase"/> class.
        /// </overloads>
        /// 
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Edenbrook.Telephony.KeypadBase"/> class
        /// with the specified <paramref name="typingTime"/> and <paramref name="waitingTime"/>.
        /// </summary>
        /// 
        /// <param name="typingTime">The time taken to type a single character.</param>
        /// <param name="waitingTime">The time taken to wait for a character to 'fix'.</param>
        /// 
        /// <remarks>None</remarks>
        protected KeypadBase(int typingTime, int waitingTime)
        {
            TypingTime = typingTime;
            WaitingTime = waitingTime;
        }
        #endregion

        // public properties (IKeypad)

        #region public int TypingTime
        /// <summary>
        /// Gets or sets the time taken to type a single character.
        /// </summary>
        /// 
        /// <value>
        /// The time taken to type a single character.
        /// </value>
        /// 
        /// <remarks>None</remarks>
        public int TypingTime
        {
            get
            {
                return _typingTime;
            }
            set
            {
                if (value < 0) throw ExceptionFactory.NumberNegative("typingTime", value);

                _typingTime = value;
            }
        }

        /// <summary>
        /// Stores the <see cref="P:Edenbrook.Telephony.KeypadBase.TypingTime"/> data.
        /// </summary>
        /// 
        /// <remarks>None</remarks>
        private int _typingTime;
        #endregion

        #region public int WaitingTime
        /// <summary>
        /// Gets or sets the time taken to wait for a character to 'fix'.
        /// </summary>
        /// 
        /// <value>
        /// The time taken to wait for a character to 'fix'.
        /// </value>
        /// 
        /// <remarks>
        /// The waiting time is only incurred when the same key needs to used for the next character.
        /// </remarks>
        public int WaitingTime
        {
            get
            {
                return _waitingTime;
            }
            set
            {
                if (value < 0) throw ExceptionFactory.NumberNegative("waitingTime", value);

                _waitingTime = value;
            }
        }

        /// <summary>
        /// Stores the <see cref="P:Edenbrook.Telephony.KeypadBase.WaitingTime"/> data.
        /// </summary>
        /// 
        /// <remarks>None</remarks>
        private int _waitingTime;
        #endregion

        // public methods (IKeypad)

        #region public int Enter(string)
        /// <summary>
        /// Returns the time taken to enter the specified <see langword="string"/> <paramref name="value"/>.
        /// </summary>
        /// 
        /// <param name="value">The <see langword="string"/> to enter.</param>
        /// 
        /// <returns>The time taken to enter the specified <see langword="string"/> <paramref name="value"/>.</returns>
        /// 
        /// <remarks>
        /// Every entry costs typing time, but only entries on the same button cost waiting time.
        /// </remarks>
        public int Enter(string value)
        {
            if (value == null) throw ExceptionFactory.ObjectNull("value");
            if (value.Length == 0) throw ExceptionFactory.StringEmpty("value");

            int time = 0;

            foreach (char character in value.ToCharArray())
            {
                time += Enter(character);
            }

            return time;
        }
        #endregion

        #region public int Enter(char)
        /// <summary>
        /// Returns the time taken to enter the specified character <paramref name="value"/>.
        /// </summary>
        /// 
        /// <param name="value">The character to enter.</param>
        /// 
        /// <returns>The time taken to enter the specified character <paramref name="value"/>.</returns>
        /// 
        /// <remarks>
        /// Every entry costs typing time, but only entries on the same button cost waiting time.
        /// </remarks>
        public int Enter(char value)
        {
            if (!ContainsKey(value)) throw ExceptionFactory.CharNotDefined("value", value);

            int time = (TypingTime * (this[value].Position + 1)) + CalculateWaitingTime(value);

            Current = value;

            return time;
        }
        #endregion

        // private properties

        #region private Char Current
        /// <summary>
        /// Gets or sets the current character.
        /// </summary>
        /// 
        /// <value>
        /// The current character.
        /// </value>
        /// 
        /// <remarks>None</remarks>
        private Char Current
        {
            get
            {
                return _current;
            }
            set
            {
                _current = value;
            }
        }

        /// <summary>
        /// Stores the <see cref="P:Edenbrook.Telephony.KeypadBase.Current"/> data.
        /// </summary>
        /// 
        /// <remarks>None</remarks>
        private Char _current;
        #endregion

        #region private int CalculateWaitingTime(char)
        /// <summary>
        /// Returns the required waiting time.
        /// </summary>
        /// 
        /// <param name="value">The character to select.</param>
        /// 
        /// <returns>
        /// The required waiting time.
        /// </returns>
        private int CalculateWaitingTime(char value)
        {
            int count = 0;

            if ((Current != (Char)0) && (this[value].Button == this[Current].Button))
            {
                foreach (KeyValuePair<Char, CharacterLocation> pair in this)
                {
                    if (pair.Value.Button == this[Current].Button) count++;
                }
            }

            return (count > 1) ? WaitingTime : 0;
        }
        #endregion
    }
}